package sk.naive.talker.server;

import sk.naive.talker.*;
import sk.naive.talker.Talker;
import sk.naive.talker.plugin.TalkerPlugin;
import sk.naive.talker.message.*;
import sk.naive.talker.util.*;
import sk.naive.talker.persistence.*;
import sk.naive.talker.command.*;

import java.util.*;
import java.util.logging.*;
import java.rmi.RemoteException;

/**
 * Command dispatcher involves command objects into command processing.
 *
 * @author <a href="mailto:virgo@naive.deepblue.sk">Richard "Virgo" Richter</a>
 * @version $Revision: 1.48 $ $Date: 2005/02/16 19:27:21 $
 */
public class CommandDispatcher
	implements sk.naive.talker.command.CommandDispatcher,
	Reloadable
{
	/** Commands logger. */
	private Logger logger;

	/** Mapping of command names to their classes. */
	private Map<String,CommandProxy> commands;

	private sk.naive.talker.server.Talker talker;

	private MessageFactory messageFactory;

	private ReloadableMessageFactory helpMessages;

	public CommandDispatcher(Talker talker)	{
		this.talker = (sk.naive.talker.server.Talker) talker;
		logger = Logger.getLogger("nt.command");
		messageFactory = new DefaultMessageFactory("sk/naive/talker/command/messages");
		helpMessages = new DefaultMessageFactory("sk.naive.talker.command.helpMessages");
		((ReloadableResources) talker.getProperties().get(ReloadableResources.TPROP_RELOAD_HELPER)).add((Reloadable) messageFactory);
		((ReloadableResources) talker.getProperties().get(ReloadableResources.TPROP_RELOAD_HELPER)).add((Reloadable) helpMessages);
		commands = new LinkedHashMap<String,CommandProxy>();

		configureCommands(getClass().getClassLoader(), Main.getConfiguration());
		((ReloadableResources) talker.getProperties().get(ReloadableResources.TPROP_RELOAD_HELPER)).add(this);
	}

	private void configureCommands(ClassLoader classLoader, Configuration configuration) {
		for (String key : configuration.getSectionProps("commands")) {
			String[] sa = key.split("\\.", 2);
			String commandName = sa[0];
			CommandProxy commandProxy = commands.get(commandName);
			if (commandProxy == null) {
				commandProxy = new CommandProxy(commandName);
				commands.put(commandName, commandProxy);
			}
			if (sa.length < 2) {
				// dummy command config - creates proxy, defines command order, usefull for plugin commands
				continue;
			}
			String property = sa[1];
			String value = configuration.get(key);
			if (property.equals("class")) {
				commandProxy.setCommandClass(loadCommandClass(classLoader, value));
			} else {
				logger.warning("Command configuration: Unknown property '" + property + "' for command '" + commandName + "'.");
			}
		}
	}

	private Class<Command> loadCommandClass(ClassLoader classLoader, String commandClassName) {
		try {
			Class<Command> cls = (Class<Command>) classLoader.loadClass(commandClassName);
			if (Command.class.isAssignableFrom(cls)) {
				return cls;
			} else {
				logger.warning("Class '" + commandClassName + "' does not implement the Command interface.");
				return null;
			}
		} catch (ClassNotFoundException e) {
			logger.log(Level.WARNING, "Problem with command class '" + commandClassName + "'.", e);
		}
		return null;
	}

	public void registerPlugin(TalkerPlugin plugin) {
		configureCommands(plugin.getClass().getClassLoader(), plugin.getConfiguration());
	}

	public void unregisterPlugin(TalkerPlugin plugin) {
		unconfigureCommands(plugin.getConfiguration());
	}

	private void unconfigureCommands(Configuration configuration) {
		for (String key : configuration.getSectionProps("commands")) {
			String[] sa = key.split("\\.", 2);
			String commandName = sa[0];
			CommandProxy commandProxy = commands.get(commandName);
			if (commandProxy == null) {
				continue;
			}
			if (commandProxy.isConfigured()) {
				commandProxy.unconfigure();
			}
		}
	}

	public void process(String cmdLine, User user)
		throws CommandException,
		RemoteException
	{
		String[] sa = Utils.splitWords(cmdLine, 2);
		String cmd = sa[0].toLowerCase();
		String param = null;
		if (sa.length > 1) {
			param = sa[1];
		}
		process(cmd, user, param);
	}

	public void process(String cmd, User u, String param)
		throws CommandException,
		RemoteException
	{
		UserContextHelper userContextHelper = new UserContextHelper(u);
		userContextHelper.set(DefaultMessageFactory.CTXKEY_VAL, cmd);
		if (cmd == null || cmd.length() == 0) {
			u.send(messageFactory.getString("command.notFound", u.getProperties()));
			return;
		}
		if (isXYCommand(cmd) && cmd.length() < 2) {
			u.send(messageFactory.getString("command.ambiguous", u.getProperties()));
			return;
		}
		cmd = Utils.findFirstInCollection(cmd, commands.keySet(), false);
		if (cmd == null) {
			u.send(messageFactory.getString("command.notFound", u.getProperties()));
			return;
		}
		CommandProxy commandProxy = commands.get(cmd);
		if (!commandProxy.isConfigured()) {
			u.send(messageFactory.getString("command.notFound", u.getProperties()));
			return;
		}
		try {
			Command c = commandProxy.createCommandInstance();
			CommandParameters parameters = new CommandParameters(this, cmd, u, param);
			c.exec(parameters);
		} catch (PermissionException e) {
			logger.log(Level.WARNING, "Missing permission (user=" + u.getLogin() + ", cmd=" + cmd + ", perm=" + e.getPermission() + ")");
			u.send(messageFactory.getString("command.missingPermission", u.getProperties()));
		} catch (InstantiationException e) {
			logger.log(Level.WARNING, "Problem instantiating command '" + cmd + "'.", e);
		} catch (IllegalAccessException e) {
			logger.log(Level.WARNING, "Problem instantiating command '" + cmd + "'.", e);
		}
		userContextHelper.cleanup();
	}

	private boolean isXYCommand(String cmd) {
		return (cmd.charAt(0) == 'x' || cmd.charAt(0) == 'y');
	}

	public Talker getTalker() {
		return talker;
	}

	public Collection<CommandProxy> getCommands() {
		return commands.values();
	}

	public Collection<String> getAvailableCommands(User u) {
		Collection<CommandProxy> commands = getCommands();
		List<String> commandList = new ArrayList<String>(commands.size());
		for (CommandProxy commandProxy : commands) {
			if (commandProxy.isConfigured()) {
				commandList.add(commandProxy.getCommandName());
			}
		}
		return commandList;
	}

	public Logger getLogger() {
		return logger;
	}

	public MessageFactory getMessageFactory() {
		return messageFactory;
	}

	public ReloadableMessageFactory getHelpMessages() {
		return helpMessages;
	}

	public UserFinder getUserFinder() {
		return talker.userFinder();
	}

	public BoardFinder boardFinder() {
		return talker.boardFinder();
	}

	public LocationFinder locationFinder() {
		return talker.locationFinder();
	}

	public ThingFinder thingFinder() {
		return talker.thingFinder();
	}

	public TextPersistence textPersistence() throws PersistenceException {
		return new DbLayer();
	}

	public void reload() {
	}
}
